package user.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.util.RandomUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import core.constants.Constant;
import core.constants.HttpConstants;
import core.domain.LoginUser;
import core.domain.Result;
import core.domain.vo.LoginUserVO;
import core.enums.ResultCode;
import core.enums.UserIdentity;
import core.exception.ServiceException;
import core.jwt.TokenService;
import core.message.AliSmsService;
import core.redis.RedisService;
import core.threadLocal.ThreadLocalUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import user.domain.User;
import user.domain.user.dto.UserDTO;
import user.domain.user.dto.UserEditDTO;
import user.domain.user.vo.UserDetailVO;
import user.manager.UserCacheManage;
import user.mapper.CUserMapper;
import user.service.IUserService;

import java.time.LocalDateTime;
import java.time.temporal.ChronoUnit;
import java.util.concurrent.TimeUnit;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

@Service
public class UserServiceImpl implements IUserService {

    @Autowired
    private AliSmsService smsService;

    @Autowired
    private RedisService redisService;

    @Autowired
    private CUserMapper userMapper;

    @Autowired
    private TokenService tokenService;

    @Autowired
    private UserCacheManage userCacheManage;

    @Value("${jwt.secret}")
    private String secret;

    @Value("${sms.code-switch: false}")  // 是否开启生成随机验证码并发送短信
    private boolean codeSwitch;

    @Value("${file.oss.headImageUrl}")
    private String headImageUrl;

    @Override
    public boolean sendCode(UserDTO userDTO) {
        if (!checkPhone(userDTO.getPhone())) {
            throw new ServiceException(ResultCode.PHONE_ERROR);
        }
        Long expire = redisService.getExpire(getPhoneCodeKey(userDTO.getPhone()));
        if(expire != null && 300 - expire < 60){
            throw new ServiceException(ResultCode.FAILED_FREQUENT);
        }

        Long sendTimes = redisService.getCacheObject(getCodeTimesKey(userDTO.getPhone()), Long.class);
        if (sendTimes != null && sendTimes >= 5) {
            throw new ServiceException(ResultCode.FAILED_CODE_TIMES_LIMIT);
        }
        String code = codeSwitch ? RandomUtil.randomNumbers(6) : "123456";
        redisService.setCacheObject(getPhoneCodeKey(userDTO.getPhone()), code, 5L, TimeUnit.MINUTES);
        if (codeSwitch) {
            boolean res = smsService.sendMobileCode(userDTO.getPhone(), code);
            if (!res) {
                throw new ServiceException(ResultCode.FAILED_CODE_SEND_ERROR);
            }
            redisService.increment(getCodeTimesKey(userDTO.getPhone()));
        }
        if(sendTimes == null) {
            // 动态计算现在到第二天剩余的时间
            long seconds = ChronoUnit.SECONDS.between(LocalDateTime.now(),
                    LocalDateTime.now().plusDays(1).withHour(0).withMinute(0)
                            .withSecond(0).withNano(0));
            redisService.expire(getCodeTimesKey(userDTO.getPhone()), seconds);
        }
        return true;
    }

    @Override
    public String codeLogin(String phone, String code) {
        User user = userMapper.selectOne(new LambdaQueryWrapper<User>()
                .eq(User::getPhone, phone));
        String phoneCodeKey = getPhoneCodeKey(phone);
        String cacheCode = redisService.getCacheObject(phoneCodeKey, String.class);
        if(StrUtil.isEmpty(cacheCode) || !cacheCode.equals(code)){
            throw new ServiceException(ResultCode.FAILED_CODE_INVALID);
        }
        // 比对成功
        redisService.deleteObject(phoneCodeKey);
        if(user == null) {
            // 新用户注册登录
            user = new User();
            user.setPhone(phone);
            userMapper.insert(user);
        }
        return tokenService.createJWT(user.getUserId(), secret, UserIdentity.NORMAL.getValue(), user.getNickName(), user.getHeadImage());
    }

    @Override
    public boolean logout(String token) {
        if (StrUtil.isNotEmpty(token) &&
                token.startsWith(HttpConstants.PREFIX)) {
            token = token.replaceFirst(HttpConstants.PREFIX, StrUtil.EMPTY);
        }
        return tokenService.deleteLoginUser(token, secret);
    }

    @Override
    public Result<LoginUserVO> info(String token) {
        if (StrUtil.isNotEmpty(token) &&
                token.startsWith(HttpConstants.PREFIX)) {
            token = token.replaceFirst(HttpConstants.PREFIX, StrUtil.EMPTY);
        }
        LoginUser loginUser = tokenService.getLoginUser(token, secret);
        if (loginUser == null) {
            return Result.fail(ResultCode.FAILED_UNAUTHORIZED);
        }
        LoginUserVO loginUserVO = new LoginUserVO();
        loginUserVO.setNickname(loginUser.getNickname());
        loginUserVO.setHeadImage(headImageUrl + loginUser.getHeadImage());
        return Result.success(loginUserVO);
    }

    @Override
    public UserDetailVO detail() {
        Long userId = ThreadLocalUtils.get(Constant.USER_ID, Long.class);
        if(userId == null){
            throw new ServiceException(ResultCode.FAILED_USER_NOT_EXISTS);
        }
        UserDetailVO userDetailVO = userCacheManage.getUserById(userId);
        if(userDetailVO == null){
            throw new ServiceException(ResultCode.FAILED_USER_NOT_EXISTS);
        }
        if(StrUtil.isNotEmpty(userDetailVO.getHeadImage())){
            userDetailVO.setHeadImage(headImageUrl + userDetailVO.getHeadImage());
        }
        return userDetailVO;
    }

    @Override
    public int edit(UserEditDTO userEditDTO) {
        Long userId = ThreadLocalUtils.get(Constant.USER_ID, Long.class);
        User user = checkUser(userId);
        BeanUtil.copyProperties(userEditDTO, user);
        // 更新缓存信息
        refreshUserInfoCache(user);
        return userMapper.updateById(user);
    }

    @Override
    public int updateHeadImage(String headImage) {
        Long userId = ThreadLocalUtils.get(Constant.USER_ID, Long.class);
        User user = checkUser(userId);
        user.setHeadImage(headImage);
        // 更新缓存信息
        refreshUserInfoCache(user);
        return userMapper.updateById(user);
    }

    private static boolean checkPhone(String phone) {
        Pattern regex = Pattern.compile("^1[3|4|5|6|7|8|9][0-9]\\d{8}$");
        Matcher m = regex.matcher(phone);
        return m.matches();
    }

    private String getPhoneCodeKey(String phone){
        return Constant.PHONE_CODE_KEY + phone;
    }

    private String getCodeTimesKey(String phone){
        return Constant.CODE_TIMES_KEY + phone;
    }

    private User checkUser(Long userId){
        if(userId == null){
            throw new ServiceException(ResultCode.FAILED_USER_NOT_EXISTS);
        }
        User user = userMapper.selectById(userId);
        if(user == null){
            throw new ServiceException(ResultCode.FAILED_USER_NOT_EXISTS);
        }
        return user;
    }

    private void refreshUserInfoCache(User user){
        tokenService.refreshLoginUser(user.getNickName(), user.getHeadImage(), user.getUserId());
        userCacheManage.refreshUserCache(user);
    }
}
